﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlTypes;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Core;
using Noris.Schedule.Planning.DataFace;
using Noris.Schedule.Support.Data;

namespace Noris.Schedule.Planning.ProcessData.Time
{
	#region CLASS Capacity : kapacitní kalendář - registruje kalendář včetně svátků, výjimky, generuje stavy kapacit
	/// <summary>
	/// Capacity : kapacitní kalendář - registruje kalendář včetně svátků, výjimky, generuje stavy kapacit
	/// </summary>
	public class Capacity
	{
		#region KONSTRUKCE
		/// <summary>
		/// Privátní konstruktor
		/// </summary>
		private Capacity() { }
		/// <summary>
		/// Vytvoří a vrátí kalendář s podporou pro tvorbu kapacitních dat
		/// </summary>
		/// <param name="planUnitC">Data kapacitní plánovací jednotky, nebud se ukládat reference na objekt, jen se z něj přečtou hodnoty.</param>
		/// <param name="findMethod">Metoda, která vrátí objekt kalendáře zdrojů</param>
		/// <param name="items">Položky odchylek tohoto kapacitního zdroje od základního kalendáře
		/// (pozor: může obsahovat i cizí položky, jde o kompletně načtená nefiltrovaná data)</param>
		/// <returns></returns>
		public static Capacity Create(PlanUnitCCls planUnitC, FindItem<Calendar> findMethod, IEnumerable<ResourcesCalendarDataCls> items)
		{
			Capacity result = new Capacity();

			// Nebudu do this ukládat referenci na planUnitC, protože by mohlo dojít k cyklické referenci.
			// Uložím jen potřebná diskrétní data:
			result.RecordNumber = planUnitC.RecordNumber;
			result.CapacityUtilization = planUnitC.CapacityUtilization;
			result.MachinesCnt = planUnitC.MachinesCnt;
			result.CalendarKey = planUnitC.ResourcesCalendar;

			// Najít a uložit obecný kalendář:
			if (result.CalendarKey != 0 && findMethod != null)
				result.CalendarData = findMethod(result.CalendarKey);

			result._FillItems(items);

			return result;
		}
		/// <summary>
		/// Naplní do svého seznamu data svátků, která odpovídají těmto svátkům (kde položka z hItms má CisloSubjektu == this.Holiday).
		/// </summary>
		/// <param name="hItms"></param>
		private void _FillItems(IEnumerable<ResourcesCalendarDataCls> rItms)
		{
			this._Items = new Dictionary<int, List<CapacityItem>>();
			int itemCnt = 0;
			int subject = this.RecordNumber;                 // Číslo subjektu této PJ
			foreach (ResourcesCalendarDataCls rItm in rItms)
			{
				if (rItm.Subject.IsNull) continue;
				if (rItm.Subject.Value != subject) continue;

				CapacityItem item = CapacityItem.Create(rItm);
				List<CapacityItem> list;
				if (!this._Items.TryGetValue(item.Key, out list))
				{
					list = new List<CapacityItem>();
					list.Add(item);
					this._Items.Add(item.Key, list);
					itemCnt++;
				}
				else
				{
					if (!list.Exists(i => i.RecordNumber == item.RecordNumber))
					{
						list.Add(item);
						itemCnt++;
					}
				}
			}
			this._ItemCount = itemCnt;
		}
		/// <summary>Data definující výjimky ze směn</summary>
		private Dictionary<int, List<CapacityItem>> _Items;
		/// <summary>Počet položek CapacityItem v this._Items</summary>
		private int _ItemCount;
		///<summary>Číslo subjektu plánovací jednotky</summary>
		public int RecordNumber { get; private set; }
		///<summary>Atribut: Vytížení kapacity [mfr_percent]</summary>
		public decimal CapacityUtilization { get; private set; }
		///<summary>Atribut: Počet jednotek [editgeneral]</summary>
		public int MachinesCnt { get; private set; }
		///<summary>Vztah 100537: Kalendář zdrojů (zprava: Kapacitní Plánovací jednotka)</summary>
		public int CalendarKey { get; private set; }
		/// <summary>Podkladový kalendář, jeho plná data (sdílený s ostatními plánovacími jednotkami)</summary>
		public Calendar CalendarData { get; private set; }
		/// <summary>Tato kapacita má navázaný kalendář</summary>
		public bool CalendarExists { get { return (this.CalendarData != null); } }
		/// <summary>Příznak, že v tomto kalendáři nejsou žádné pracovní směny (s kladnou velikostí)</summary>
		public bool IsEmpty { get { return !(this.CalendarExists && !this.CalendarData.IsEmpty); } }
		/// <summary>Počet položek, které definují tento kapacitní kalendář</summary>
		internal int DefItemCount { get { return this._ItemCount + (this.CalendarExists ? this.CalendarData.DefItemCount : 0); } }
		#endregion
		#region TVORBA STAVU KAPACIT
		/// <summary>
		/// Vygeneruje a vrátí seznam stavů kapacit pro jeden měsíc. Měsíc je odvozen z daného datumu.
		/// Vrácený seznam je setříděn datem počátku stavu kapacit.
        /// Objekt this (třídy Capacity) je vytvořen specificky pro konkrétní kapacitní jednotku (každá KPJ má svůj objekt Capacity).
		/// </summary>
		/// <param name="timeRange">Období</param>
		/// <returns></returns>
		internal List<CapacityLevelItemCls> CreateCapacityLevel(TimeRange timeRange)
		{
			// Krok 1: pro daný interval vytvořím časovou osu, která ponese změny kapacity (plus/mínus) dané definicí:
			decimal utilization = this.CapacityUtilization / 100M;   // Využití kapacity (1.00 = 100%)
			List<CapacityChangeItem> timeChanges = new List<CapacityChangeItem>();
			DateTime date = timeRange.Begin.Date;
			while (date < timeRange.End)
			{
                // Pro každý (i sváteční) den přidám na časovou osu moje soukromé výjimky, a nastavím hasChanges, pokud tento den mám soukromé změny:
                bool hasChanges = this.AddOneDayChanges(date, utilization, timeChanges);

                // Pokud nemám soukromé změny, pak pokud není svátek, pak na časovou osu zapíšu definice opakovaných směn podle kalendáře:
                if (!hasChanges)
    				this.CalendarData.AddOneDayChanges(date, this.MachinesCnt, utilization, timeChanges);

				// Další den:
				date = date.AddDays(1).Date;
			}
			// Setřídíme časovou osu:
			timeChanges.Sort(CapacityChangeItem.CompareByChange);

			// Krok 2: z časové osy vygeneruji stavy kapacit.
			// Komentáře:
			//  Na časové ose jsou záznamy o změně stavu (_ChangeType.Change) a o nastavení stavu (_ChangeType.Set, _ChangeType.Release).
			//  Změna stavu je kumulativní (změna o +10 a o -2 dá ve výsledku stav 8).
			//  Nastavení stavu je typu "poslední má pravdu" s chováním typu "Zásobník" = když je více změn přes sebe, 
			//    pak po skončení (_ChangeType.Release) druhé se vrací k platnosti ta předchozí, pokud má ještě platný čas.
			//  Kombinace principu kumulativního a principu nastavení: 
			//    - Nastavení stavu má vyšší prioritu. 
			//    - Po ukončení posledního nastavení se vracíme k hodnotám kumulativním, 
			//      které se napočítávají (na pozadí) i v době, kdy platí hodnoty "nastavené".
			List<CapacityLevelItemCls> result = new List<CapacityLevelItemCls>();     // Výsledné stavy kapacit
			List<CapacityChangeItem> setList = new List<CapacityChangeItem>();        // Registr vnořených změn typu Set

			StateTLDS flow = new StateTLDS();           // Průběžný stav, kumulovaný ze změn typu Change
			StateTLDS begin = new StateTLDS();          // Počáteční stav, z něhož se bude začínat stav kapacit
			StateTLDS end = new StateTLDS();            // Nalezený konec. Pokud bude příští změna v témž čase, přepíše se. Jinak se uloží úsek {begin÷end} a z end se stane nový begin.

			DateTime lastBeginTime = DateTime.MinValue;
			int lastBeginLines = 0;
			decimal lastBeginDensity = 0M;
			DateTime currentTime = DateTime.MinValue;
			int currentLines = lastBeginLines;
			decimal currentDensity = lastBeginDensity;
			for (int i = 0; i < timeChanges.Count; i++)
			{
				CapacityChangeItem changeItem = timeChanges[i];
				// Určíme, jaký stav bude platit od této změny:
				StateTLDS current = new StateTLDS();    // Stav, který bude platit od tohoto okamžiku.
				current.Time = changeItem.ItemDate;
				switch (changeItem.ChangeType)
				{
					case CapacityChangeType.Change:
						// Změna průběžného stavu:
						flow.LineCount = flow.LineCount + changeItem.ChangeLines;
						flow.CapacityDensity = flow.CapacityDensity + changeItem.ChangeCapacityDensity;
						flow.WorkShift = changeItem.WorkShift;
						if (setList.Count == 0)
						{	// Pokud v tuto chvíli není aktivní žádný úsek typu CapacityDefinitionType.ExchangeOfWorkShift:
							//  - změna se promítá do aktuálního stavu:
							current.LineCount = flow.LineCount;
							current.CapacityDensity = flow.CapacityDensity;
							current.WorkShift = flow.WorkShift;
						}
						else
						{	// V tuto chvíli je aktivní úsek typu CapacityDefinitionType.ExchangeOfWorkShift:
							//  - tato změna se na venek nepromítá.
							current.Time = changeItem.ItemDate;
							current.LineCount = end.LineCount;
							current.CapacityDensity = end.CapacityDensity;
							current.WorkShift = end.WorkShift;
							// Může ale dojít ke změně čísla pracovní směny, 
							//  což by mi mohlo "rozdělit" výjimkový čas např. na "ranní" a "odpolední" směnu:
							if (flow.WorkShift != 0)
								// Je zde zadaná směna => převzít ji do current, ať se příště vyhodnotí její změna:
								current.WorkShift = flow.WorkShift;
						}
						break;
					case CapacityChangeType.Set:
						// Zahájení úseku typu CapacityDefinitionType.ExchangeOfWorkShift:
						setList.Add(changeItem);
						current.LineCount = changeItem.ChangeLines;
						current.CapacityDensity = changeItem.ChangeCapacityDensity;
						current.WorkShift = flow.WorkShift;       // Výjimka přebírá číslo směny z právě aktuální, kumulativní
						break;
					case CapacityChangeType.Release:
						// Konec platnosti některého úseku typu CapacityDefinitionType.ExchangeOfWorkShift:
						// Najdeme a odebereme paměť hodnot fixního úseku, který v teto čas končí:
						int setIdx = setList.FindIndex(delegate(CapacityChangeItem item) { return (item.ItemDefId == changeItem.ItemDefId); });
						if (setIdx >= 0)
							setList.RemoveAt(setIdx);
						// Je tu ještě nějaký jiný fixní úsek?
						int lastSet = setList.Count - 1;
						if (lastSet >= 0)
						{	// Po odebrání daného bloku je aktivní jiný, předchozí blok typu CapacityDefinitionType.ExchangeOfWorkShift:
							current.LineCount = setList[lastSet].ChangeLines;
							current.CapacityDensity = setList[lastSet].ChangeCapacityDensity;
							current.WorkShift = flow.WorkShift;   // Směnu přebírám z průběžného čísla směny. Tam se vepisuje číslo směny při zahájení řádných směn.
						}
						else
						{	// Po odebrání daného bloku již neexistují další fixní bloky, přecházíme na hodnoty flow:
							current.LineCount = flow.LineCount;
							current.CapacityDensity = flow.CapacityDensity;
							current.WorkShift = flow.WorkShift;
						}
						break;
				}
				//   Pokud současná změna proběhla "pod povrchem", neovlivní nám vývoj stavu kapacit:
				//   (Pod povrchem = v čase, kdy platí fixní úsek, jsme realizovali změnu hodnoty flow.)
				//   if (current.Time == DateTime.MinValue) continue;

				// Nyní víme, že počínaje aktuálním časem máme mít určité stavy (current).
				// Tady dojde k převodu z jednorozměrného světa (hodnota) na dvojrozměrné hodnoty (čas od÷do + hodnoty pro tento čas).
				// Abychom nevytvářeli zbytečné bloky (kdy hned po sobě budou navazovat identické hodnoty), je zde následující logika:

				// Počátek je prázdný? Do počátku vložíme current a půjdeme na další čas:
				if (begin.IsEmpty)
				{
					begin = current;
					continue;
				}
				// Pokud je end prázdný, vložíme do end hodnotu current a půjdeme na další čas:
				// Anebo pokud end a current jsou ze shodného času, pak do end vložíme current údaje a půjdeme dál.
				//  Tím se řeší situace, kdy je v jednom čase zaznamenáno více změn, a budeme reagovat až na tu poslední:
				if (end.IsEmpty || end.Time == current.Time)
				{
					end = current;
					continue;
				}

				// Nyní je zřetelné, že begin začíná nějaký časový úsek, end jej ukončuje, a současný current je až poté.
				// Z hodnot begin a end vytvoříme stav kapacit (a dáme do výstupu), a připravíme si nový begin z end..
				if (begin.Time < end.Time && StateTLDS.NonEqual(begin, end))
				{
                    if (StateTLDS.CanCreateCapacityLevel(begin, end))                   // if (begin.Time < end.Time && (begin.LineCount > 0 || begin.CapacityDensity > 0M))
						result.Add(StateTLDS.CreateCapacityLevelItem(begin, end));
					begin = end;
				}
				end = current;
			}

			// Po dokončení může v proměnných zůstat něco pro poslední stav kapacit:
			if (StateTLDS.CanCreateCapacityLevel(begin, end))
				result.Add(StateTLDS.CreateCapacityLevelItem(begin, end));

			return result;
		}
		/// <summary>
		/// Na danou časovou osu vygeneruje záznamy o změnách disponibilní kapacity za daný den.
        /// Tato metoda zpracovává výjimky z kalendáře definované pro aktuální subjekt.
        /// Objekt this (třídy Capacity) je vytvořen specificky pro konkrétní kapacitní jednotku (každá KPJ má svůj objekt Capacity).
		/// </summary>
		/// <param name="date">Datum pro tvorbu záznamů o změnách kapacity</param>
		/// <param name="utilization">Využití kapacity (1.00 = 100%)</param>
		/// <param name="timeChanges">Seznam změn kapacity</param>
		internal bool AddOneDayChanges(DateTime date, decimal utilization, List<CapacityChangeItem> timeChanges)
		{
            bool result = false;
			List<CapacityItem> defList;
			if (this._Items.TryGetValue(CalItem.GetKeyForExplicitDay(date), out defList))
			{	// Pokud pro tento den máme definované nějaké výjimky, pak je zpracuji
                if (defList.Any(i => i.IsForPlanCVersion) && defList.Any(i => !i.IsForPlanCVersion))
                    // Pokud pro tento den máme výjimky bez verze a současně i pro verzi, pak budu akceptovat jen výjimky platné pro naši verzi:
                    defList = defList.Where(i => i.IsForPlanCVersion).ToList();
				foreach (CapacityItem item in defList)
				{	// Za každou směnu zadám jednu změnu Začátek a jednu změnu Konec:
					int defId = timeChanges.Count / 2 + 1;          // ID identifikuje dvojici záznamů Begin a End mezi sebou.
					timeChanges.Add(item.CreateChangeBegin(date, utilization, defId));
					timeChanges.Add(item.CreateChangeEnd(date, utilization, defId));
				}
                result = true;
			}
            return result;
		}
		#endregion
		#region SUBSTRUCT StateTLDS : STRUKTURA (!) OBSAHUJÍCÍ ČAS, LINE, DENSITY, SHIFT a umožňující porovnání dvou pozic se změnou
		/// <summary>
		/// StateTLDS: Struktura, které obsahuje data pro jeden okamžik na časové ose.
		/// Data = čas, počet LineCount, hustota kapacity, číslo směny.
		/// </summary>
		private struct StateTLDS
		{
			/// <summary>
			/// Čas, od něhož platí zde uvedená data
			/// </summary>
			public DateTime Time;
			/// <summary>
			/// Počet linek v tomto čase
			/// </summary>
			public int LineCount;
			/// <summary>
			/// Hustota kapacity = kapacita na 1 sekundu času
			/// </summary>
			public decimal CapacityDensity;
			/// <summary>
			/// Číslo směny
			/// </summary>
			public int WorkShift;
			/// <summary>
			/// Vizualizace
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				return "{" + Time.ToString() + ": Line=" + LineCount.ToString() + "; Dens=" + CapacityDensity.ToString() + "; Ws=" + WorkShift.ToString() + "}";
			}
			/// <summary>
			/// true, pokud je tento vektor prázdný - tj. jeho datum Time je prázdné.
			/// </summary>
			public bool IsEmpty { get { return (Time == DateTime.MinValue); } }
			/// <summary>
			/// Vrací true, pokud mezi strukturou A a B je nenulový čas, a je třeba vytvořit závěrečný stav kapacit.
			/// Ten se vytvoří i tehdy, když kapacity v bodě B jsou shodné jako v bodě A.
			/// Pro vytvoření stavu kapacit existuje metoda _StateTLDSstr.CreateCapacityLevelItem().
			/// </summary>
			/// <param name="a"></param>
			/// <param name="b"></param>
			/// <returns></returns>
			public static bool CanCreateCapacityLevel(StateTLDS a, StateTLDS b)
			{
				return (b.Time > a.Time && (a.LineCount > 0 && a.CapacityDensity > 0M));
			}
			/// <summary>
			/// Vrací true, pokud obě struktury obsahují shodná data ve všech položkách, 
			/// přičemž shoda směny (WorkShift) je povinná jen pokud jsou obě směny vyplněny.
			/// </summary>
			/// <param name="a"></param>
			/// <param name="b"></param>
			/// <returns></returns>
			public static bool Equal(StateTLDS a, StateTLDS b)
			{
				return (
					a.Time == b.Time &&
					a.LineCount == b.LineCount &&
					a.CapacityDensity == b.CapacityDensity &&
					EqualShift(a, b));
			}
			/// <summary>
			/// Vrací true, pokud záznamy (a) a (b) obsahují odlišná data.
			/// </summary>
			/// <param name="a"></param>
			/// <param name="b"></param>
			/// <returns></returns>
			public static bool NonEqual(StateTLDS a, StateTLDS b)
			{
				// Shodný čas vrátí vždy false (nejde o rozdíl):
				if (a.Time == b.Time) return false;

				// Čas je jiný: pokud bude rozdíl v počtu linek nebo v CapacityDensity, vrátím true (jde o rozdíl):
				if (a.LineCount != b.LineCount || a.CapacityDensity != b.CapacityDensity) return true;

				// Čas je jiný, ale počet linek a hustota jsou shodné. Pokud se liší směna, vrátím true = jde o rozdíl:
				if (NonEqualShift(a, b)) return true;

				// Zdá se, že by mohlo jít o pokračující směnu, která je rozdělena jen formálně do dvou definic.
				// Ale pokud je vzdálenost mezi počátkem a koncem směny rovna nebo větší než 24 hodin, vrátím true = je třeba směnu rozdělit:
				TimeSpan time = b.Time - a.Time;
				if (time.TotalHours >= 24D) return true;

				// Spojíme dvě směny do jedné:
				return false;
			}
			/// <summary>
			/// Vrací true, pokud obě struktury obsahují shodné směny.
			/// Shoda směny (WorkShift) je true, pokud některá směna není vyplněna, anebo jsou obě vyplněny shodně.
			/// </summary>
			/// <param name="a"></param>
			/// <param name="b"></param>
			/// <returns></returns>
			public static bool EqualShift(StateTLDS a, StateTLDS b)
			{
				return (a.WorkShift == 0 || b.WorkShift == 0 || a.WorkShift == b.WorkShift);
			}
			/// <summary>
			/// Vrací true, pokud obě struktury obsahují zadanou směnu, a každá jinou.
			/// Shoda směny (WorkShift) je true, pokud některá směna není vyplněna, anebo jsou obě vyplněny shodně.
			/// </summary>
			/// <param name="a"></param>
			/// <param name="b"></param>
			/// <returns></returns>
			public static bool NonEqualShift(StateTLDS a, StateTLDS b)
			{
				return (a.WorkShift != 0 && b.WorkShift != 0 && a.WorkShift != b.WorkShift);
			}
			/// <summary>
			/// Vytvoří a vrátí stav kapacit ze dvou časů
			/// </summary>
			/// <param name="begin"></param>
			/// <param name="end"></param>
			/// <returns></returns>
			public static CapacityLevelItemCls CreateCapacityLevelItem(StateTLDS begin, StateTLDS end)
			{
				TimeRange timeRange = new TimeRange(begin.Time, end.Time);
				decimal capacity = (decimal)begin.LineCount * begin.CapacityDensity * (decimal)timeRange.Seconds;
				CapacityLevelItemCls level = new CapacityLevelItemCls(timeRange, begin.LineCount, capacity, begin.WorkShift);
				return level;
			}
		}
		#endregion
	}
	#region CLASS CapacityItem : jedna položka definice výjimky z pracovní směny
	/// <summary>
	/// CapacityItem : jedna položka definice výjimky z pracovní směny
	/// </summary>
	internal class CapacityItem : CalendarItem
	{
		#region KONSTRUKCE
		protected CapacityItem(int recordNumber, CapacityDefinitionType definitionType)
			: base(recordNumber, definitionType)
		{ }
		internal static new CapacityItem Create(ResourcesCalendarDataCls data)
		{
			CapacityDefinitionType definitionType = CapacityDefinitionType.ExchangeOfWorkShift;
			TimeRange time = GetGreenTimeRange(data.StartInterval, data.StopInterval, false);
			int lineCnt = 0;
			if (!data.PocetJednotek.IsNull)                        // Počet linek je ve výjimce zadán, převezmu jej:
				lineCnt = data.PocetJednotek.Value;
			else if (time.Seconds > 0D)                            // Nezadaný počet linek : odvodím jej z kapacity a času (když je čas):
				lineCnt = (int)(Math.Ceiling((double)data.EffectiveCapacity / time.Seconds));
			else
				lineCnt = 0;

			CapacityItem item = new CapacityItem(data.RecordNumber, definitionType);
			item.Time = time;
			item.ShiftCode = GetShiftCode(data.Workshift, time.Begin);
			item.LineCount = lineCnt;
            item.PlanCVersion = data.PlanCVersion.ToIntN();
			item.Capacity = data.EffectiveCapacity;
			return item;
		}
		#endregion
		#region PROPERTY (nad rámec CalendarItem)
		/// <summary> { get; }
		/// Počet současných linek, může se lišit od počtu v kapacitní jednotce (o tom je právě ta změna oproti kalendáři)
		/// </summary>
		internal int LineCount { get; private set; }
        /// <summary>
        /// Verze plánu: může být null (když výjimka platí pro všechny verze) nebo konkrétní číslo (pro tuto verzi plánu).
        /// </summary>
        internal int? PlanCVersion { get; private set; }
        /// <summary>
        /// true pokud tato výjimka je pro aktuální verzi plánu, false když je bez verze
        /// </summary>
        internal bool IsForPlanCVersion { get { return (this.PlanCVersion.HasValue); } }
		#endregion
		#region TVORBA ZÁZNAMU O ZMĚNĚ KAPACITY
		/// <summary>
		/// Vytvoří záznam o začátku období s danou kapacitou
		/// </summary>
		/// <param name="date">Konkrétní datum</param>
		/// <param name="lineCount">Počet linek</param>
		/// <param name="defId">ID změny, páruje se se svým koncem (má shodné ID)</param>
		/// <returns></returns>
		internal CapacityChangeItem CreateChangeBegin(DateTime date, decimal utilization, int defId)
		{
			bool exchange = (this.DefinitionType == CapacityDefinitionType.ExchangeOfWorkShift);
			
			CapacityChangeItem change = new CapacityChangeItem();
			change.ItemDefId = defId;
			change.ItemDate = this.GetBeginForDate(date);
			change.ChangeType = (exchange ? CapacityChangeType.Set : CapacityChangeType.Change);
			change.ChangeLines = this.LineCount;
			change.ChangeCapacity = (decimal)this.LineCount * utilization * this.Capacity;
			change.ChangeCapacityDensity = this.CapacityDensity;
			change.WorkShift = this.ShiftCode;
			return change;
		}
		/// <summary>
		/// Vytvoří záznam o konci období s danou kapacitou
		/// </summary>
		/// <param name="date">Konkrétní datum</param>
		/// <param name="lineCount">Počet linek</param>
		/// <param name="defId">ID změny, páruje se se svým začátkem (má shodné ID)</param>
		/// <returns></returns>
		internal CapacityChangeItem CreateChangeEnd(DateTime date, decimal utilization, int defId)
		{
			bool exchange = (this.DefinitionType == CapacityDefinitionType.ExchangeOfWorkShift);
			
			CapacityChangeItem change = new CapacityChangeItem();
			change.ItemDefId = defId;
			change.ItemDate = this.GetEndForDate(date);
			change.ChangeType = (exchange ? CapacityChangeType.Release : CapacityChangeType.Change);
			change.ChangeLines = -this.LineCount;
			change.ChangeCapacity = -1 * ((decimal)this.LineCount * utilization * this.Capacity);
			change.ChangeCapacityDensity = -this.CapacityDensity;
			change.WorkShift = this.ShiftCode;
			return change;
		}
		#endregion
	}
	#endregion
	#endregion
	#region CLASS Calendar : obecně fungující kalendář zdrojů
	/// <summary>
	/// Calendar : obecně fungující kalendář zdrojů
	/// </summary>
	public class Calendar : IReferNazev
	{
		#region KONSTRUKCE
		/// <summary>
		/// Privátní konstruktor
		/// </summary>
		private Calendar() {}
		/// <summary>
		/// Vytvoří a vrátí obecný kalendář
		/// </summary>
		/// <param name="resourceData">Data kalendáře</param>
		/// <param name="findMethod">Metoda, která vrátí objekt svátků pro daný kalendář</param>
		/// <param name="items">Položky definic kalendáře (pozor: může obsahovat i cizí položky, jde o kompletně načtená nefiltrovaná data)</param>
		/// <returns></returns>
		public static Calendar Create(ResourcesCalendarCls resourceData, FindItem<Holidays> findMethod, IEnumerable<ResourcesCalendarDataCls> items)
		{
			Calendar result = new Calendar();

			result.ResourceData = resourceData;
			result.ApplyHoliday = false;

			bool applyHoliday = String.Equals(resourceData.ApplyHoliday, "A", StringComparison.OrdinalIgnoreCase);
			int holidayKey = (resourceData.Holiday.IsNull ? 0 : resourceData.Holiday.Value);
			if (applyHoliday && holidayKey > 0 && findMethod != null)
			{
				result.HolidayData = findMethod(holidayKey);
				result.ApplyHoliday = (result.HolidayData != null);
			}

			result._FillItems(items);

			return result;
		}
		/// <summary>
		/// Naplní do svého seznamu data svátků, která odpovídají těmto svátkům (kde položka z hItms má CisloSubjektu == this.Holiday).
		/// </summary>
		/// <param name="hItms"></param>
		private void _FillItems(IEnumerable<ResourcesCalendarDataCls> rItms)
		{
			this._Items = new Dictionary<int, List<CalendarItem>>();
			bool isEmpty = true;
			int itemCnt = 0;
			int calendar = this.RecordNumber;                 // Číslo subjektu tohoto kalendáře
			foreach (ResourcesCalendarDataCls rItm in rItms)
			{
				if (rItm.ResourcesCalendarDef.IsNull) continue;
				if (rItm.ResourcesCalendarDef.Value != calendar) continue;

				CalendarItem item = CalendarItem.Create(rItm);
                if (item != null)
                {
                    if (isEmpty && item.Time.Seconds > 0D)
                        isEmpty = false;
                    List<CalendarItem> list;
                    if (!this._Items.TryGetValue(item.Key, out list))
                    {
                        list = new List<CalendarItem>();
                        list.Add(item);
                        this._Items.Add(item.Key, list);
                        itemCnt++;
                    }
                    else
                    {
                        if (!list.Exists(i => i.RecordNumber == item.RecordNumber))
                        {
                            list.Add(item);
                            itemCnt++;
                        }
                    }
                }
			}
			this._ItemCount = itemCnt;
			this.IsEmpty = isEmpty;
		}
		/// <summary>
		/// Data definující kalendář (hlavička)
		/// </summary>
		internal ResourcesCalendarCls ResourceData { get; private set; }
		/// <summary>
		/// Data svátků
		/// </summary>
		internal Holidays HolidayData { get; private set; }
		/// <summary>
		/// Data definující pravidelné směny
		/// </summary>
		Dictionary<int, List<CalendarItem>> _Items;
		/// <summary>Počet položek CapacityItem v this._Items</summary>
		private int _ItemCount;
		#endregion
		#region PUBLIC DATA PROPERTY
		/// <summary>Číslo kalendáře</summary>
		public int RecordNumber { get { return this.ResourceData.RecordNumber; } }
		///<summary>Atribut: Uplatnit svátky (Požadavek a svátky jsou zadané)</summary>
		public bool ApplyHoliday { get; private set; }
		///<summary>Číslo sady svátků</summary>
		public int HolidayKey { get { return (this.ResourceData.Holiday.IsNull ? 0 : this.ResourceData.Holiday.Value);} }
		///<summary>Atribut: Povinnost směny</summary>
		public bool ShiftLiable { get { return String.Equals(this.ResourceData.ShiftLiable, "A", StringComparison.OrdinalIgnoreCase); } }
		/// <summary>Příznak, že v tomto kalendáři nejsou žádné pracovní směny (s kladnou velikostí)</summary>
		public bool IsEmpty { get; private set;}
		/// <summary>Počet položek, které definují tento kalendář</summary>
		internal int DefItemCount { get { return this._ItemCount; } }
        /// <summary>Číslo třídy Kalendář zdrojů = 1196</summary>
        public const int ClassNr = ResourcesCalendarCls.ClassNr;
        /// <summary>Reference a název záznamu</summary>
        public string ReferNazev { get { return this.ResourceData.ReferNazev; } }
		#endregion
		#region GENERÁTOR PLATNÝCH ČASŮ TOHOTO KALENDÁŘE, HLEDÁNÍ PRACOVNÍHO ČASU, DETEKCE SVÁTKU
		/// <summary>
		/// Vytvoří a vrátí seznam časových intervalů, kdy obsahuje tento kalendář aktivní časy (podle definičních položek mínus svátky).
		/// Vrací tedy jen seznam časových úseků, kdy se pracuje, vyjma svátků.
		/// Vrácené úseky nemají údaje o kapacitě.
		/// </summary>
		/// <param name="interval"></param>
		/// <returns></returns>
		public List<TimeRange> CreateIntervals(TimeRange interval, bool sort)
		{
			List<TimeRange> result = new List<TimeRange>();
			List<CalendarItem> defList;
			DateTime date = interval.Begin.Date;
			while (date <= interval.End.Date)
			{
				if (this.IsHoliday(date)) continue;

				if (this._Items.TryGetValue(CalItem.GetKeyForWorkShift(date), out defList))
				{
					foreach (CalendarItem item in defList)
						result.Add(item.GetTimeForDate(date)); // Za každou definici přidám její časový interval pro konkrétní datum
				}
				date = date.AddDays(1D).Date;                  // Další den (čas 00:00)
			}
			if (sort)
				result.Sort(TimeRange.CompareByBeginAsc);
			return result;
		}
		/// <summary>
		/// Vrátí definice pro daný jeden den, pokud ten den není akceptovaným svátkem.
		/// Pokud den je svátkem, anebo pokud není směna, vrací null.
		/// </summary>
		/// <param name="date"></param>
		/// <param name="timeDir"></param>
		/// <returns></returns>
		internal List<TimeRange> CreateOneDayIntervalsSorted(DateTime date, TimeRange.TimeDirection timeDir)
		{
            return this.CreateOneDayIntervalsSorted(date, timeDir, null);
        }
        /// <summary>
        /// Vrátí definice pro daný jeden den, pokud ten den není akceptovaným svátkem.
        /// Pokud den je svátkem, anebo pokud není směna, vrací null.
        /// Do výstupu vkládá pouze časové intervaly vyhovující danému meznímu datu a směru času, oříznuté mezním časem.
        /// </summary>
        /// <param name="date"></param>
        /// <param name="timeDir"></param>
        /// <returns></returns>
        internal List<TimeRange> CreateOneDayIntervalsSorted(DateTime date, TimeRange.TimeDirection timeDir, DateTime? edge)
        {
            List<TimeRange> result = null;
			if (!this.IsHoliday(date))
			{
				List<CalendarItem> defList;
				if (this._Items.TryGetValue(CalItem.GetKeyForWorkShift(date), out defList))
				{
					result = new List<TimeRange>();
                    foreach (CalendarItem item in defList)
                    {
                        TimeRange itemTime = item.GetTimeForDate(date, timeDir, edge);
                        if (!itemTime.IsEmpty)
                            result.Add(itemTime);
                    }
                    result.Sort(timeDir);
				}
			}
			return result;
		}
		/// <summary>
		/// Na danou časovou osu vygeneruje záznamy o změnách disponibilní kapacity za daný den.
		/// </summary>
		/// <param name="date">Datum pro tvorbu záznamů o změnách kapacity</param>
		/// <param name="lineCount">Počet výrobních linek</param>
		/// <param name="utilization">Využití kapacity (1.00 = 100%)</param>
		/// <param name="timeChanges">Seznam změn kapacity</param>
		internal void AddOneDayChanges(DateTime date, int lineCount, decimal utilization, List<CapacityChangeItem> timeChanges)
		{
			if (this.IsHoliday(date)) return;         // Svátek: nepracuje se.

			List<CalendarItem> defList;
			if (this._Items.TryGetValue(CalItem.GetKeyForWorkShift(date), out defList))
			{	// Pokud pro tento den pracujeme:
				foreach (CalendarItem item in defList)
				{	// Za každou směnu zadám jednu změnu Začátek a jednu změnu Konec:
					int defId = timeChanges.Count / 2 + 1;      // ID identifikuje dvojici záznamů Begin a End mezi sebou.
					timeChanges.Add(item.CreateChangeBegin(date, lineCount, utilization, defId));
					timeChanges.Add(item.CreateChangeEnd(date, lineCount, utilization, defId));
				}
			}
		}
		/// <summary>
		/// Najde nejbližší časový interval k danému dni a času, v daném směru.
		/// Pokud nenajde nic, vrátí TimeRange.Empty.
		/// K tomu dojde a) když tento kalendář neobsahuje žádné položky (pak má this.IsEmpty == true)
		/// anebo b) když má tak řídké směny, že během 3 měsíců nenajde ani jednu (pak to vzdá).
		/// </summary>
		/// <param name="dateTime">Hledaný čas</param>
		/// <param name="timeDir">Směr hledání času</param>
		/// <returns></returns>
		public TimeRange FindNearestInterval(DateTime dateTime, TimeRange.TimeDirection timeDir)
		{
			return this.FindNearestInterval(dateTime, timeDir, false);
		}
		/// <summary>
		/// Najde nejbližší časový interval k danému dni a času, v daném směru.
		/// Pokud nenajde nic, vrátí TimeRange.Empty.
		/// K tomu dojde a) když tento kalendář neobsahuje žádné položky (pak má this.IsEmpty == true)
		/// anebo b) když má tak řídké směny, že během 3 měsíců nenajde ani jednu (pak to vzdá).
		/// </summary>
		/// <param name="dateTime">Hledaný čas</param>
		/// <param name="timeDir">Směr hledání času</param>
		/// <param name="withZeroTime">Akceptovat i nulý časový úsek. Default = false (nulu nebrat, najít příští úsek).
		/// Tj. např. při hledání času 20.2.2010 12:00, ToFuture, lze akceptovat interval {20.2.2010 8:00 ÷ 20.2.2010 12:00},
		/// tedy takový, který v daném čase právě končí (vzhledem ke směru času), anebo který má rozměr 0 sekund.</param>
		/// <returns></returns>
		public TimeRange FindNearestInterval(DateTime dateTime, TimeRange.TimeDirection timeDir, bool withZeroTime)
		{
			if (this.IsEmpty) return TimeRange.Empty;
			int dir = TimeRange.ConvertDirectionToInt32(timeDir);       // ToHistory = -1;   ToFuture = +1;  jinak 0
			if (dir == 0)
				Throw.SysError(MessageInfo.Get("Nelze hledat časový interval metodou Calendar.FindNearestInterval() bez zadání směru času."));

			DateTime date = dateTime.Date;
			for (int to = 0; to < 92; to++)
			{	// (to je jen timeout: 92 dnů = 3 měsíce)
				List<TimeRange> defList = this.CreateOneDayIntervalsSorted(date, timeDir);
				if (defList != null)
				{	// Časy pro tento den:
					foreach (TimeRange time in defList)
					{	// Platná definice času kalendáře:
						if (!withZeroTime && time.Seconds <= 0) continue;  // Nulový interval neberu, pokud je withZeroTime == false (nechci prázdné časy)

						switch (timeDir)
						{
							case TimeRange.TimeDirection.ToFuture:
								// Hledám do budoucího času: časy jsou setříděny podle Begin, ASC
								// Najdu první čas, který končí po požadovaném čase:
								if (time.End > dateTime || (withZeroTime && time.End == dateTime))
									return time;
								break;

							case TimeRange.TimeDirection.ToHistory:
								// Hledám do minulého času: časy jsou setříděny podle End, DESC
								// Najdu první čas, který začíná před požadovaným časem:
								if (time.Begin < dateTime || (withZeroTime && time.Begin == dateTime))
									return time;
								break;
						}
					}
				}
				date = date.AddDays(dir);          // Další den (+- 1)
			}
			return TimeRange.Empty;
		}
		/// <summary>
		/// Vrátí true, když tento kalendář akceptuje svátky, a daný den je svátek.
		/// Pokud tento kalendář svátky neakceptuje, vrátí vždy false.
		/// </summary>
		/// <param name="date">Dotazovaný den</param>
		/// <returns></returns>
		public bool IsHoliday(DateTime date)
		{
			if (!this.ApplyHoliday) return false;
			return this.HolidayData.IsHoliday(date);
		}
		#endregion
        #region VÝPOČET CÍLOVÉHO DATA A ČASU PRO ZADANÉ DATUM VÝCHOZÍ, SMĚR HLEDÁNÍ A DANÝ INTERVAL (obdoba určení kapacity, ale jen v rámci kalendáře)
        /// <summary>
        /// Určí koncové datum a čas, při zadaném okamžiku startu, daném směru hledání a daném intervalu (čas) v tomto kalendáři.
        /// Slouží k určení data a času, kdy se dokončí daný počet sekund (interval).
        /// Výsledek může být null tehdy, když direction není (ToFuture nebo ToHistory), když time je záporný, nebo když time je tak velký, že datum vyjde mimo rozsah.
        /// </summary>
        /// <param name="start">Okamžik zahájení</param>
        /// <param name="timeDir">Směr času</param>
        /// <param name="time">Časová kapacita</param>
        /// <returns></returns>
        public DateTime? FindTime(DateTime start, TimeRange.TimeDirection timeDir, decimal time)
        {
            List<TimeRange> timeList = this.FindTimeList(start, timeDir, time);
            if (timeList == null || timeList.Count == 0) return null;

            switch (timeDir)
            {
                case TimeRange.TimeDirection.ToFuture:
                    return timeList[timeList.Count - 1].End;
                case TimeRange.TimeDirection.ToHistory:
                    return timeList[0].Begin;
            }

            return null;
        }
        /// <summary>
        /// Vytvoří a vrátí seznam všech časových úseků, které jsou zapotřebí pro naplnění určité časové kapacity (time = sekundy),
        /// počínaje daným okamžikem (start) v daném směru.
        /// Metoda vrací seznam všech časových úseků, počínaje start, v daném směru, jejichž součet časů = time.
        /// Pokud směr času není (ToFuture nebo ToHistory), anebo když time je záporný, vrací null.
        /// Rovněž vrací null, pokud tento kalendář je Empty, anebo když požadavek (time) přesahuje únosné meze (roky 1500 - 2500).
        /// </summary>
        /// <param name="start">Okamžik zahájení</param>
        /// <param name="timeDir">Směr času</param>
        /// <param name="time">Časová kapacita</param>
        /// <returns></returns>
        public List<TimeRange> FindTimeList(DateTime start, TimeRange.TimeDirection timeDir, decimal time)
        {
            if (this.IsEmpty) return null;
            if (!(timeDir == TimeRange.TimeDirection.ToFuture || timeDir == TimeRange.TimeDirection.ToHistory)) return null;
            if (time < 0m) return null;

            List<TimeRange> timeList = new List<TimeRange>();

            decimal remaining = time;
            DateTime current = start;
            double stepDays = (timeDir == TimeRange.TimeDirection.ToHistory ? -1d : 1d);        // Počet dní do příštího dne (ToHistory / ToFuture)

            while (true)
            {
                List<TimeRange> defList = this.CreateOneDayIntervalsSorted(current.Date, timeDir, start);
                if (defList != null && defList.Count > 0)
                {   // Seznam časů (za jeden den): je setříděn: ToFuture podle Begin ASC, pro ToHistory podle End DESC.
                    // Seznam obsahuje položky, vyhovující datu start a směru timeDir, případná první položka je oříznutá datem start.
                    //    Nemusíme tedy řešit první den, první čas, první částečný interval atd.
                    foreach (TimeRange defItem in defList)
                    {   // Jednotlivé aktivní časy v rámci jednoho dne:
                        TimeRange timeItem = FindTimeItem(defItem, start, timeDir, ref remaining);
                        if (!timeItem.IsEmpty)
                            timeList.Add(timeItem);
                        if (timeList.Count > 0 && remaining <= 0m)
                            break;
                    }
                }
                if (timeList.Count > 0 && remaining <= 0m)
                    break;

                current = current.Date.AddDays(stepDays);
                if (current.Year < 1500 || current.Year > 2500) return null;                    // Out of sense
            }

            return timeList;
        }
        /// <summary>
        /// Vrátí časový úsek vytvořený z dané definice, daného času počátku a směru, do něhož z dané definice odčerpá nejvýše daný počet sekund.
        /// Pokud je na vstupu interval (pro ukázku uvedeme numericky): {100-250}, start = 120, timeDir je ToFuture, a required je 80, pak je vytvořen a vrácen interval {120-200} (120 je start, délka je 80). Hodnota required je změněna na 0.
        /// Pokud je zadání podobné, jen start = 200, pak je vrácen interval {200-250} (protože vstupní interval končil na 250), a hodnota required je změněna na 30 (požadováno 80, k dispozici je jen 50, zbývá 30).
        /// Pokud hodnota start leží z pohledu timeDir za vstupním intervalem, vrací se Empty.
        /// Pokud požadovaný čas required je záporný, vrací se Empty.
        /// Pokud požadovaný čas required je nula, vrací se časový interval v délce 0 sekund v odpovídajícím čase.
        /// </summary>
        /// <param name="timeRange">Vstupní časový interval</param>
        /// <param name="start">Počáteční čas, od kdy lze čerpat čas</param>
        /// <param name="timeDir">Směr čerpání času</param>
        /// <param name="required">IN: Požadovaný čas (sekundy);  OUT: Zbývající čas k dalšímu hledání</param>
        /// <returns></returns>
        public static TimeRange FindTimeItem(TimeRange timeRange, DateTime start, TimeRange.TimeDirection timeDir, ref decimal required)
        {
            if (required < 0m) return TimeRange.Empty;         // Požadavek je záporný

            TimeRange result = timeRange;
            switch (timeDir)
            {
                case TimeRange.TimeDirection.ToFuture:
                    if (start > result.Begin)
                        result.Begin = start;
                    break;
                case TimeRange.TimeDirection.ToHistory:
                    if (start < result.End)
                        result.End = start;
                    break;
            }

            decimal seconds = (decimal)result.Seconds;
            if (seconds < 0m) return TimeRange.Empty;          // Čas start je za daným intervalem.

            if (required < seconds)
            {   // Potřebujeme méně, než máme k dispozici - zkrátíme interval a vynulujeme požadavek:
                result.SetSeconds((double)required, timeDir);
                required = 0m;
            }
            else if (required == seconds)
            {   // Potřebujeme stejně jako je k dispozici - vezmeme vše a vynulujeme požadavek:
                required = 0m;
            }
            else
            {   // Potřebujeme více než je k dispozici - vezmeme vše a snížíme požadavek:
                required -= seconds;
            }

            return result;
        }
        #endregion
    }
	#region CLASS CalendarItem : jedna položka definice kalendáře
	/// <summary>
	/// CalendarItem : jedna položka definice kalendáře
	/// </summary>
	internal class CalendarItem : CalItem
	{
		#region KONSTRUKCE
		protected CalendarItem(int recordNumber, CapacityDefinitionType definitionType)
			: base(recordNumber, definitionType)
		{ }
		internal static CalendarItem Create(ResourcesCalendarDataCls data)
		{
			CapacityDefinitionType definitionType = CapacityDefinitionType.DayWorkShift;
			TimeRange time = GetGreenTimeRange(data.StartInterval, data.StopInterval, true);
            if (time.IsEmpty) return null;

			CalendarItem item = new CalendarItem(data.RecordNumber, definitionType);
			item.Time = time;
			item.ShiftCode = GetShiftCode(data.Workshift, time.Begin);
			item.Capacity = data.EffectiveCapacity;
			return item;
		}
		/// <summary>
		/// Vrátí číslo směny podle daných dat.
		/// Pokud je shiftCode == null, pak vrátí číslo odvozené jako 24 x den + hodina z daného času.
		/// Jinak vrátí shiftCode.Value
		/// </summary>
		/// <param name="shiftCode"></param>
		/// <param name="begin"></param>
		/// <returns></returns>
		internal static int GetShiftCode(SqlInt32 shiftCode, DateTime begin)
		{
			if (shiftCode.IsNull)
				return 24 * begin.Day + begin.Hour;
			return shiftCode.Value;
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// Datum položky (datum svátku, datum počátku intervalu)
		/// </summary>
		public override DateTime Date { get { return this.Time.Begin; } }
		/// <summary>
		/// true, pokud tato definice vyjadřuje běžnou opakovanou pracovní dobu. Ve dny, kdy je svátek, se tato doba neuplatní.
		/// </summary>
		public bool IsStandardWork { get { return this.DefinitionType == CapacityDefinitionType.DayWorkShift; } }
		/// <summary>
		/// Časový interval této definice
		/// </summary>
		public TimeRange Time { get; protected set; }
		/// <summary>
		/// Číslo směny
		/// </summary>
		public int ShiftCode { get; protected set; }
		/// <summary>
		/// Kapacita v tomto období.
        /// Obsahuje kapacitu jedné linky (=hustotu).
        /// Bázová třída (CalendarItem) deklaruje jednu linku.
        /// Potomek (CapacityItem) zde ukládá rovněž kapacitu jedné linky, nenásobí ji počtem linek (počet linek má v jiné property).
		/// </summary>
		public decimal Capacity { get; protected set; }
		/// <summary>
		/// Hustota kapacity v tomto úseku. Jde o poměr využitelné kapacity k fyzickému času.
        /// Zcela typická hodnota je 1.00.
		/// Pokud je například třeba plánovat jen 80% kapcity (a 20% nechávat vždy nevyužité, například pro údržbu), 
		/// pak je zde hodnota = 0.8
		/// </summary>
		public decimal CapacityDensity
		{
			get
			{
				if (this.Time.Seconds > 0D)
					return (decimal)this.Capacity / (decimal)this.Time.Seconds;
				return 0M;
			}
		}
		#endregion
		#region PODPŮRNÉ METODY
		/// <summary>
		/// Vrátí čas Begin podle této definice, pro konkrétní datum.
		/// Pokud v this je typ dat == DayWorkShift (definice směny), pak vrátí čas pro dané datum (parametr), 
		/// ale čas převezme z časů this.Time.Begin.
		/// Pro jiné typy dat vrací this.Time.Begin beze změn.
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		internal DateTime GetBeginForDate(DateTime date)
		{
			return GetTimeForDate(date).Begin;
		}
		/// <summary>
		/// Vrátí čas End podle této definice, pro konkrétní datum.
		/// Pokud v this je typ dat == DayWorkShift (definice směny), pak vrátí čas pro dané datum (parametr), 
		/// ale čas převezme z časů this.Time.End.
		/// Pro jiné typy dat vrací this.Time.End beze změn.
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		internal DateTime GetEndForDate(DateTime date)
		{
			return GetTimeForDate(date).End;
		}
		/// <summary>
		/// Vrátí časový interval podle této definice, pro konkrétní datum.
		/// Pokud v this je typ dat == DayWorkShift (definice směny), pak vrátí interval pro dané datum (parametr), 
		/// ale čas převezme z časů this.Time.
		/// Pro jiné typy dat vrací this.Time beze změn.
		/// </summary>
		/// <param name="date"></param>
		/// <returns></returns>
		internal TimeRange GetTimeForDate(DateTime date)
		{
			if (this.DefinitionType == CapacityDefinitionType.DayWorkShift)
				return new TimeRange(date.Date + this.Time.Begin.TimeOfDay, this.Time.Seconds);
			return this.Time;
		}
        /// <summary>
        /// Vrátí časový interval podle této definice, pro konkrétní datum.
        /// Výsledný interval ořízne v daném směru danou hranou.
        /// Pokud tedy bude například vytvořen časový interval (20.1.2013 10:00 - 20.1.2013 16:00), a bude zadán směr ToFuture a čas edge = 20.1.2013 11:15, 
        /// pak výsledkem této metody bude oříznutý interval : (20.1.2013 11:15 - 20.1.2013 16:00).
        /// Pokud výsledek oříznutí bude prázdný, vrátí se Empty.
        /// </summary>
        /// <param name="date"></param>
        /// <param name="timeDir"></param>
        /// <param name="edge"></param>
        /// <returns></returns>
        internal TimeRange GetTimeForDate(DateTime date, TimeRange.TimeDirection timeDir, DateTime? edge)
        {
            TimeRange time = this.GetTimeForDate(date);
            if (edge.HasValue && (timeDir == TimeRange.TimeDirection.ToHistory || timeDir == TimeRange.TimeDirection.ToFuture))
                // Máme zadané hodnoty pro ořezávání:
                time.SplitMe(edge.Value, timeDir);
            return time;
        }
        #endregion
		#region TVORBA ZÁZNAMU O ZMĚNĚ KAPACITY
		/// <summary>
		/// Vytvoří záznam o začátku období s danou kapacitou
		/// </summary>
		/// <param name="date">Konkrétní datum</param>
		/// <param name="lineCount">Počet linek</param>
		/// <param name="defId">ID změny, páruje se se svým koncem (má shodné ID)</param>
		/// <returns></returns>
		internal CapacityChangeItem CreateChangeBegin(DateTime date, int lineCount, decimal utilization, int defId)
		{
			CapacityChangeItem change = new CapacityChangeItem();
			change.ItemDefId = defId;
			change.ItemDate = this.GetBeginForDate(date);
			change.ChangeType = CapacityChangeType.Change;
			change.ChangeLines = lineCount;
			change.ChangeCapacity = (decimal)lineCount * utilization * this.Capacity;
			change.ChangeCapacityDensity = this.CapacityDensity;
			change.WorkShift = this.ShiftCode;
			return change;
		}
		/// <summary>
		/// Vytvoří záznam o konci období s danou kapacitou
		/// </summary>
		/// <param name="date">Konkrétní datum</param>
		/// <param name="lineCount">Počet linek</param>
		/// <param name="defId">ID změny, páruje se se svým začátkem (má shodné ID)</param>
		/// <returns></returns>
		internal CapacityChangeItem CreateChangeEnd(DateTime date, int lineCount, decimal utilization, int defId)
		{
			CapacityChangeItem change = new CapacityChangeItem();
			change.ItemDefId = defId;
			change.ItemDate = this.GetEndForDate(date);
			change.ChangeType = CapacityChangeType.Change;
			change.ChangeLines = -lineCount;
			change.ChangeCapacity = -1 * ((decimal)lineCount * utilization * this.Capacity);
			change.ChangeCapacityDensity = -this.CapacityDensity;
			change.WorkShift = this.ShiftCode;
			return change;
		}
		#endregion
    }
	#endregion
	#endregion
	#region CLASS Holidays : svátky
	/// <summary>
	/// Seznam svátků
	/// </summary>
	public class Holidays
	{
		#region KONSTRUKCE
		/// <summary>
		/// Privátní konstruktor
		/// </summary>
		private Holidays() { }
		/// <summary>
		/// Vytvoří soupis svátků pro daný klíč.
		/// Na vstupu musí být seznam položek = jednotlivé svátky. Z něj si převezme jen ty položky, kde CisloSubjektu == this.Holiday.
		/// </summary>
		/// <param name="holiday"></param>
		/// <param name="items"></param>
		public static Holidays Create(int holiday, IEnumerable<HolidayItmCls> items)
		{
			Holidays result = new Holidays();
			result.Holiday = holiday;
			result._FillItems(items);
			return result;
		}
		/// <summary>
		/// Naplní do svého seznamu data svátků, která odpovídají těmto svátkům (kde položka z hItms má CisloSubjektu == this.Holiday).
		/// </summary>
		/// <param name="hItms"></param>
		private void _FillItems(IEnumerable<HolidayItmCls> hItms)
		{
			this._Items = new Dictionary<int, List<HolidayItem>>();
			int holiday = this.Holiday;                 // Číslo subjektu těchto svátků
			foreach (HolidayItmCls hItm in hItms)
			{
				if (hItm.CisloSubjektu != holiday) continue;

				HolidayItem item = HolidayItem.Create(hItm);
				List<HolidayItem> list;
				if (!this._Items.TryGetValue(item.Key, out list))
				{
					list = new List<HolidayItem>();
					list.Add(item);
					this._Items.Add(item.Key, list);
				}
				else
				{
					if (!list.Exists(i => i.RecordNumber == item.RecordNumber))
						list.Add(item);
				}
			}
		}
		/// <summary>
		/// Číslo svátků (číslo subjektu)
		/// </summary>
		public int Holiday { get; private set; }
		/// <summary>
		/// Soupis svátků, členěný po dnech
		/// </summary>
		private Dictionary<int, List<HolidayItem>> _Items;
		#endregion
		#region DETEKCE, ZDA DANÝ DEN JE SVÁTEK
		/// <summary>
		/// Zjistí, zda daný den je svátkem podle tohoto soupisu
		/// </summary>
		/// <param name="dateTime">Dotazovaný den</param>
		/// <returns></returns>
		public bool IsHoliday(DateTime date)
		{
			bool isHoliday = false;
			bool disabledHoliday = false;
			List<HolidayItem> holidays = this._GetHolidaysForDate(date.Date);
			foreach (HolidayItem item in holidays)
			{
				switch (item.DefinitionType)
				{
					case CapacityDefinitionType.MovableHoliday:
						isHoliday = true;
						break;
					case CapacityDefinitionType.YearlyHoliday:
						isHoliday = true;
						break;
					case CapacityDefinitionType.DisableHoliday:
						disabledHoliday = true;
						break;
				}
			}
			return (isHoliday && !disabledHoliday);
		}
		/// <summary>
		/// Vrátí souhrn svátků pro konkrétní den.
		/// Souhrn zahrnuje svátky pevné, pohyblivé a výjimky ze svátků.
		/// </summary>
		/// <param name="dateTime"></param>
		/// <returns></returns>
		private List<HolidayItem> _GetHolidaysForDate(DateTime dateTime)
		{
			List<HolidayItem> result = new List<HolidayItem>();
			List<HolidayItem> detect;

			// Svátky definované na konkrétní rok:
			if (this._Items.TryGetValue(CalItem.GetKeyForMovableHoliday(dateTime), out detect))
				result.AddRange(detect);
			// Svátky definované pro každý rok (jsou uložené pod klíč, jehož rok == 0):
			if (_Items.TryGetValue(CalItem.GetKeyForYearlyHoliday(dateTime), out detect))
				result.AddRange(detect);

			return result;
		}
		#endregion
	}
	#region CLASS HolidayItem : jedna položka svátku
	/// <summary>
	/// HolidayItem : jedna položka svátku
	/// </summary>
	internal class HolidayItem : CalItem
	{
		#region KONSTRUKCE
		protected HolidayItem(int recordNumber, CapacityDefinitionType definitionType)
		: base(recordNumber, definitionType)
		{ }
		internal static HolidayItem Create(HolidayItmCls data)
		{
			CapacityDefinitionType definitionType = (data.FixedDate == "A" ? CapacityDefinitionType.MovableHoliday : CapacityDefinitionType.YearlyHoliday);
			HolidayItem item = new HolidayItem(data.RecordNumber, definitionType);
			item.HolidayDate = data.DateHoliday;
			return item;
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// Datum položky (datum svátku, datum počátku intervalu)
		/// </summary>
		public override DateTime Date { get { return this.HolidayDate; } }
		/// <summary>
		/// Datum svátku
		/// </summary>
		public DateTime HolidayDate { get; protected set; }
		#endregion
	}
	#endregion
	#endregion
	#region CLASS CapacityChangeItem : JEDNA ZMĚNA NA ČASOVÉ OSE, ENUM CapacityChangeType : TYP ZMĚNY
	/// <summary>
	/// CapacityChangeItem : třída, která zaznamenává na časovou osu jednotkovou změnu kapacity.
	/// </summary>
	internal class CapacityChangeItem
	{
		/// <summary>
		/// Konstruktor
		/// </summary>
		internal CapacityChangeItem()
		{ }
		/// <summary>
		/// ID změny, počátek a konec jej mají shodné.
		/// </summary>
		public int ItemDefId;
		/// <summary>
		/// Datum změny
		/// </summary>
		public DateTime ItemDate;
		/// <summary>
		/// Typ změny
		/// </summary>
		public CapacityChangeType ChangeType;
		/// <summary>
		/// Vliv změny: Počet linek
		/// </summary>
		public int ChangeLines;
		/// <summary>
		/// Vliv změny: Kapacita
		/// </summary>
		public decimal ChangeCapacity;
		/// <summary>
		/// Hustota kapacity = kapacita na jednu sekundu plného času definice.
		/// Protože se definice může uplatnit třeba jen z části (protože některou její část může změnit jiná definice), 
		/// je třeba znát relativní hustotu kapacity. Ta pak figuruje ve výpočtech, které určují kapacitu poměrné části časového úseku.
		/// </summary>
		public decimal ChangeCapacityDensity;
		/// <summary>
		/// Číslo směny
		/// </summary>
		public int WorkShift;
		/// <summary>
		/// Komparátor pro setřídění časové osy
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns></returns>
		public static int CompareByChange(CapacityChangeItem x, CapacityChangeItem y)
		{
			int result;
			result = x.ItemDate.CompareTo(y.ItemDate);
			if (result != 0) return result;              // Rozdíl v datu: jasná priorita, tím je vyřešeno.

			// Co dřív a co pak, v rámci jednoho data: mínus dřív, plus potom:
			return x.ChangeCapacity.CompareTo(y.ChangeCapacity);
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			string result = "{ID=" + this.ItemDefId.ToString() + "; Date=" + this.ItemDate.ToString() + "; Type=" + this.ChangeType.ToString();
			switch (this.ChangeType)
			{
				case CapacityChangeType.Change:
					result += ": Lines += " + this.ChangeLines.ToString();
					break;
				case CapacityChangeType.Set:
					result += ": Lines = " + this.ChangeLines.ToString();
					break;
				case CapacityChangeType.Release:
					result += "; Reset lines";
					break;
			}
			return result + "; Ws=" + this.WorkShift.ToString() + "}";
		}
	}
	/// <summary>
	/// Druh změny kapacity
	/// </summary>
	internal enum CapacityChangeType
	{
		/// <summary>
		/// Změna hodnot o hodnoty zde uvedené
		/// </summary>
		Change,
		/// <summary>
		/// Nastavení hodnot zde uvedených
		/// </summary>
		Set,
		/// <summary>
		/// Ukončení platnosti hodnot z tohoto ID
		/// </summary>
		Release 
	}
	#endregion
	#region CLASS CalItem : základní třída pro třídy položek ve svátcích, kalendářích a kapacitách
	/// <summary>
	/// CalItem : základní třída pro položky v kalendářích
	/// </summary>
	internal abstract class CalItem
	{
		internal CalItem(int recordNumber, CapacityDefinitionType definitionType)
		{
			this.RecordNumber = recordNumber;
			this.DefinitionType = definitionType;
		}
		/// <summary>
		/// Číslo záznamu
		/// </summary>
		public int RecordNumber { get; private set; }
		/// <summary>
		/// Druh definice
		/// </summary>
		public CapacityDefinitionType DefinitionType { get; private set; }
		/// <summary>
		/// Datum položky (datum svátku, datum počátku intervalu)
		/// </summary>
		public abstract DateTime Date { get; }
		/// <summary>
		/// int klíč této položky v časových dictionary (odpovídá datu a typu položky)
		/// </summary>
		public int Key { get { return _GetDateKey(this.DefinitionType, this.Date); } }
		#region PŘEVOD DATA NA KLÍČ
		/// <summary>
		/// Vrátí číslo Int pro čas směny
		/// </summary>
		/// <param name="time"></param>
		/// <returns></returns>
		internal static int GetKeyForWorkShift(DateTime time)
		{ return (int)time.DayOfWeek; }
		/// <summary>
		/// Vrátí číslo Int pro datum svátku, který se každoročně opakuje (štědrý den)
		/// </summary>
		/// <param name="time"></param>
		/// <returns></returns>
		internal static int GetKeyForYearlyHoliday(DateTime time)
		{ return time.Month * 100 + time.Day; }
		/// <summary>
		/// Vrátí číslo Int pro běžné datum (je identické jako datum svátku, který platí jen v konkrétní rok (velikonoce))
		/// </summary>
		/// <param name="time"></param>
		/// <returns></returns>
		internal static int GetKeyForExplicitDay(DateTime time)
		{ return time.Year * 10000 + time.Month * 100 + time.Day; }
		/// <summary>
		/// Vrátí číslo Int pro datum svátku, který platí jen v konkrétní rok (velikonoce)
		/// </summary>
		/// <param name="time"></param>
		/// <returns></returns>
		internal static int GetKeyForMovableHoliday(DateTime time)
		{ return time.Year * 10000 + time.Month * 100 + time.Day; }
		/// <summary>
		/// Vrátí číslo, které odpovídá datu dané položky
		/// </summary>
		/// <param name="defType"></param>
		/// <param name="holidayDate"></param>
		/// <param name="timeBegin"></param>
		/// <returns></returns>
		private static int _GetDateKey(CapacityDefinitionType defType, DateTime date)
		{
			switch (defType)
			{
				case CapacityDefinitionType.DayWorkShift:
					// Den v týdnu:
					return GetKeyForWorkShift(date);

				case CapacityDefinitionType.AddToWorkShift:
				case CapacityDefinitionType.ExchangeOfWorkShift:
					// Explicitní datum:
					return GetKeyForExplicitDay(date);

				case CapacityDefinitionType.YearlyHoliday:
					// Každoroční svátek, bez ohledu na rok:
					return GetKeyForYearlyHoliday(date);

				case CapacityDefinitionType.MovableHoliday:
				case CapacityDefinitionType.DisableHoliday:
					return GetKeyForMovableHoliday(date);
			}
			return 0;
		}
		#endregion
		#region PŘEVOD DATA Begin, End NA TimeRange
		/// <summary>
		/// Z dodaných datumů Green vygeneruje a vrátí explicitní časový interval.
		/// Specifikum Greenu: pokud je datum stejné, a čas = 00:01:00 jak pro Begin, tak pro End, pak jde o celodenní časový interval.
		/// </summary>
		/// <param name="begin"></param>
		/// <param name="end"></param>
		/// <param name="definitionType">Typ definice: pokud je DayWorkShift, pak bude vrácen interval podle dohody o definici pracovní doby (pak pro čas 00:01-00:01 vrátím Empty)</param>
		/// <returns></returns>
		internal static TimeRange GetGreenTimeRange(DateTime begin, DateTime end, CapacityDefinitionType definitionType)
		{
			return GetGreenTimeRange(begin, end, (definitionType == CapacityDefinitionType.DayWorkShift));
		}
		/// <summary>
		/// Z dodaných datumů Green vygeneruje a vrátí explicitní časový interval.
		/// Specifikum Greenu: pokud je datum stejné, a čas = 00:01:00 jak pro Begin, tak pro End, pak jde o celodenní časový interval.
		/// </summary>
		/// <param name="begin">Počátek uvedený v datech Green</param>
		/// <param name="end">Konec uvedený v datech Green</param>
		/// <param name="isDefinition">true, pokud data pocházejí z definice pracovní doby (pak pro čas 00:01-00:01 vrátím Empty)</param>
		/// <returns></returns>
		internal static TimeRange GetGreenTimeRange(DateTime begin, DateTime end, bool isDefinition)
		{
			DateTime beginDate = begin.Date;
			bool sameDate = (beginDate == end.Date);
			bool sameTime = (begin.Hour == end.Hour && begin.Minute == end.Minute && begin.Second == end.Second);
			int beginMinute = begin.Hour * 60 + begin.Minute;           // Počet minut dne začátku
			int endMinute = end.Hour * 60 + end.Minute;                 // Počet minut dne konce
			if (sameDate && sameTime)
			{	// Stejné datum, stejný čas:
				if (beginMinute == 0)
				{	// Čas 00:00 ÷ 00:00 :  
                    //   V definici i ve výjimce značí celý den.
                    //   Ve výjimce by mělo být definován počet jednotek.
                    return new TimeRange(begin.Date, 86400D);           // Vždy (definice i výjimka) = celý den od 00:00 do 24:00
				}
				else if (beginMinute == 1)
				{	// Čas 00:01 ÷ 00:01 :  
                    if (isDefinition)
    					return TimeRange.Empty;                         // V definici: značí prázdnou směnu (typicky sobota, neděle)
                    return new TimeRange(begin.Date, end.Date);         // Ve výjimce: vrátím datum a čas 0:00, jeho kapacita = 0, ale obsadí mi daný den namísto definice
				}
				return TimeRange.Empty;
			}
			else if (sameDate && end < begin && endMinute == 0)
			{	// Stejné datum, ale čas konce je nižší než čas počátku, čas konce je 00:00 => takhle v Greenu končí noční směna 
				//  (například pondělí 1.1. 22:00 až 1.1. 00:00) => převedeme konec na příští den:
				end = end.AddDays(1);
			}
			return new TimeRange(begin, end);
		}
		#endregion
	}
	#endregion
}
